package org.g4studio.core.mvc.xstruts.action;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.g4studio.core.mvc.xstruts.config.ForwardConfig;
import org.g4studio.core.mvc.xstruts.util.ResponseUtils;

/**
 * <p>
 * A subclass of {@link ActionForward} which is designed for use in redirecting
 * requests, with support for adding parameters at runtime. <br/>
 * An {@link ForwardConfig} (or subclass) can be passed to the constructor to
 * copy its configuration:
 * </p>
 * <p>
 * 
 * <pre>
 * public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request,
 * 		HttpServletResponse response) throws Exception {
 * 	ActionRedirect redirect = new ActionRedirect(mapping.findForward(&quot;doRedirect&quot;));
 * 	redirect.addParameter(&quot;param1&quot;, &quot;value1&quot;);
 * 	redirect.addParameter(&quot;param2&quot;, &quot;2&quot;);
 * 	redirect.addParameter(&quot;param3&quot;, &quot;3.0&quot;);
 * 	return redirect;
 * }
 * </pre>
 * 
 * </p>
 * 
 * @version $Rev: 421119 $ $Date: 2006-07-11 21:49:11 -0700 (Tue, 11 Jul 2006) $
 */
public class ActionRedirect extends ActionForward {
	// ----------------------------------------------------- Manifest constants

	/**
	 * <p>
	 * Default allocation size for string buffers.
	 * </p>
	 */
	private static final int DEFAULT_BUFFER_SIZE = 256;

	// ----------------------------------------------------- Static variables

	/**
	 * <p>
	 * Commons logging instance.
	 * </p>
	 */
	protected static final Log LOG = LogFactory.getLog(ActionRedirect.class);

	// ----------------------------------------------------- Instance variables

	/**
	 * <p>
	 * Holds the redirect parameters. Each entry is either a String or a
	 * String[] depending on whether it has one or more entries.
	 * </p>
	 */
	protected Map parameterValues = null;

	/**
	 * <p>
	 * Holds the anchor value.
	 * </p>
	 */
	protected String anchorValue = null;

	// ----------------------------------------------------- Constructors

	/**
	 * <p>
	 * Construct a new instance with redirect set to true and initialize
	 * parameter lists.
	 * </p>
	 */
	public ActionRedirect() {
		setRedirect(true);
		initializeParameters();
	}

	/**
	 * <p>
	 * Construct a new instance with the specified path and initialize parameter
	 * lists.
	 * </p>
	 * 
	 * @param path
	 *            Path for this instance
	 */
	public ActionRedirect(String path) {
		super(path);
		setRedirect(true);
		initializeParameters();
	}

	/**
	 * <p>
	 * Construct a new instance with the specified values and initialize
	 * parameter lists.
	 * </p>
	 * 
	 * @param name
	 *            Name of this instance
	 * @param path
	 *            Path for this instance
	 * @param module
	 *            Module prefix, if any
	 */
	public ActionRedirect(String name, String path, String module) {
		super(name, path, true);
		setModule(module);
		initializeParameters();
	}

	/**
	 * <p>
	 * Construct a new instance with a {@link ForwardConfig} object to copy
	 * name, path, and contextRelative values from.
	 * </p>
	 * 
	 * @param baseConfig
	 *            the {@link ForwardConfig} to copy configuration values from
	 */
	public ActionRedirect(ForwardConfig baseConfig) {
		setName(baseConfig.getName());
		setPath(baseConfig.getPath());
		setModule(baseConfig.getModule());
		setRedirect(baseConfig.getRedirect());
		initializeParameters();
	}

	// ----------------------------------------------------- Private methods

	/**
	 * <p>
	 * Initializes the internal objects used to hold parameter values.
	 * </p>
	 */
	private void initializeParameters() {
		parameterValues = new HashMap();
	}

	// ----------------------------------------------------- Public methods

	/**
	 * <p>
	 * Adds the object's toString() to the list of parameters if it's not null,
	 * or an empty string with the given fieldName if it is.
	 * </p>
	 * 
	 * @param fieldName
	 *            the name to use for the parameter
	 * @param valueObj
	 *            the value for this parameter
	 * @return The ActionRedirect instance this method is called on
	 */
	public ActionRedirect addParameter(String fieldName, Object valueObj) {
		String value = (valueObj != null) ? valueObj.toString() : "";

		if (parameterValues == null) {
			initializeParameters();
		}

		// try {
		value = ResponseUtils.encodeURL(value);

		// } catch (UnsupportedEncodingException uce) {
		// this shouldn't happen since UTF-8 is the W3C Recommendation
		// String errorMsg = "UTF-8 Character Encoding not supported";
		// LOG.error(errorMsg, uce);
		// throw new RuntimeException(errorMsg, uce);
		// }
		Object currentValue = parameterValues.get(fieldName);

		if (currentValue == null) {
			// there's no value for this param yet; add it to the map
			parameterValues.put(fieldName, value);
		} else if (currentValue instanceof String) {
			// there's already a value; let's use an array for these parameters
			String[] newValue = new String[2];

			newValue[0] = (String) currentValue;
			newValue[1] = value;
			parameterValues.put(fieldName, newValue);
		} else if (currentValue instanceof String[]) {
			// add the value to the list of existing values
			List newValues = new ArrayList(Arrays.asList((Object[]) currentValue));

			newValues.add(value);
			parameterValues.put(fieldName, newValues.toArray(new String[newValues.size()]));
		}
		return this;
	}

	/**
	 * <p>
	 * Adds an anchor to the path. Technically, the anchor value is just stored
	 * for later and will be added to the path in getPath(). Note that this is a
	 * considerably simpler method than the addParmaeter method because aside
	 * from encoding the value, there isn't really anything to do. Passing in
	 * null is fine because that is the value that will be checked for later to
	 * determine whether to append an anchor to the path or not.
	 * </p>
	 * 
	 * @param anchorValue
	 *            The anchor to append to the path
	 * @return The ActionRefirect instance this method is called on
	 */
	public ActionRedirect setAnchor(String anchorValue) {
		this.anchorValue = ResponseUtils.encodeURL(anchorValue);
		return this;
	}

	/**
	 * <p>
	 * Get the original path without the parameters added at runtime.
	 * </p>
	 * 
	 * @return the original path as configured.
	 */
	public String getOriginalPath() {
		return super.getPath();
	}

	/**
	 * <p>
	 * Get the path for this object, including any parameters that may have been
	 * added at runtime.
	 * </p>
	 * 
	 * @return The path for this object.
	 */
	public String getPath() {
		// get the original path and the parameter string that was formed
		String originalPath = getOriginalPath();
		String parameterString = getParameterString();
		String anchorString = getAnchorString();

		StringBuffer result = new StringBuffer(originalPath);

		if ((parameterString != null) && (parameterString.length() > 0)) {
			// the parameter separator we're going to use
			String paramSeparator = "?";

			// true if we need to use a parameter separator after originalPath
			boolean needsParamSeparator = true;

			// does the original path already have a "?"?
			int paramStartIndex = originalPath.indexOf("?");

			if (paramStartIndex > 0) {
				// did the path end with "?"?
				needsParamSeparator = (paramStartIndex != (originalPath.length() - 1));

				if (needsParamSeparator) {
					paramSeparator = "&";
				}
			}

			if (needsParamSeparator) {
				result.append(paramSeparator);
			}

			result.append(parameterString);
		}

		// append anchor string (or blank if none was set)
		result.append(anchorString);

		return result.toString();
	}

	/**
	 * <p>
	 * Forms the string containing the parameters passed onto this object thru
	 * calls to addParameter().
	 * </p>
	 * 
	 * @return a string which can be appended to the URLs. The return string
	 *         includes a leading hash mark (#).
	 */
	public String getAnchorString() {
		String retVal = "";
		if (anchorValue != null) {
			retVal = "#" + anchorValue;
		}
		return retVal;
	}

	/**
	 * <p>
	 * Forms the string containing the parameters passed onto this object thru
	 * calls to addParameter().
	 * </p>
	 * 
	 * @return a string which can be appended to the URLs. The return string
	 *         does not include a leading question mark (?).
	 */
	public String getParameterString() {
		StringBuffer strParam = new StringBuffer(DEFAULT_BUFFER_SIZE);

		// loop through all parameters
		Iterator iterator = parameterValues.keySet().iterator();

		while (iterator.hasNext()) {
			// get the parameter name
			String paramName = (String) iterator.next();

			// get the value for this parameter
			Object value = parameterValues.get(paramName);

			if (value instanceof String) {
				// just one value for this param
				strParam.append(paramName).append("=").append(value);
			} else if (value instanceof String[]) {
				// loop through all values for this param
				String[] values = (String[]) value;

				for (int i = 0; i < values.length; i++) {
					strParam.append(paramName).append("=").append(values[i]);

					if (i < (values.length - 1)) {
						strParam.append("&");
					}
				}
			}

			if (iterator.hasNext()) {
				strParam.append("&");
			}
		}

		return strParam.toString();
	}

	// ----------------------------------------------------- toString()

	/**
	 * <p>
	 * Return a string description of this object.
	 * </p>
	 * 
	 * @return a string containing the original path for this object and the
	 *         parameters it currently holds
	 */
	public String toString() {
		StringBuffer result = new StringBuffer(DEFAULT_BUFFER_SIZE);

		result.append("ActionRedirect [");
		result.append("originalPath=").append(getOriginalPath()).append(";");
		result.append("parameterString=").append(getParameterString()).append("]");
		result.append("anchorString=").append(getAnchorString()).append("]");

		return result.toString();
	}
}
